package com.hqd.ch03.v32.tx.transaction.config;

import com.hqd.ch03.utils.DomUtils;
import com.hqd.ch03.v32.config.BeanDefinition;
import com.hqd.ch03.v32.factory.config.RuntimeBeanNameReference;
import com.hqd.ch03.v32.factory.config.RuntimeBeanReference;
import com.hqd.ch03.v32.factory.config.TypedStringValue;
import com.hqd.ch03.v32.factory.support.ManagedMap;
import com.hqd.ch03.v32.reader.xml.ParserContext;
import com.hqd.ch03.v32.reader.xml.parse.BeanDefinitionParser;
import com.hqd.ch03.v32.tx.transaction.interceptor.*;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Element;

import java.util.ArrayList;
import java.util.List;

public class TxAdviceBeanDefinitionParser implements BeanDefinitionParser {
    private static final String ADVICE_ID = "id";
    private static final String METHOD_ELEMENT = "method";

    private static final String METHOD_NAME_ATTRIBUTE = "name";

    private static final String ATTRIBUTES_ELEMENT = "attributes";

    private static final String TIMEOUT_ATTRIBUTE = "timeout";

    private static final String READ_ONLY_ATTRIBUTE = "read-only";

    private static final String PROPAGATION_ATTRIBUTE = "propagation";

    private static final String ISOLATION_ATTRIBUTE = "isolation";

    private static final String ROLLBACK_FOR_ATTRIBUTE = "rollback-for";

    private static final String NO_ROLLBACK_FOR_ATTRIBUTE = "no-rollback-for";


    private BeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) {
        /**
         * 获取method标签
         */
        List<Element> methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);
        ManagedMap<TypedStringValue, RuleBasedTransactionAttribute> transactionAttributeMap =
                new ManagedMap<>(methods.size());

        for (Element methodEle : methods) {
            //解析方法名
            String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE);
            TypedStringValue nameHolder = new TypedStringValue(name);
            /**
             * 解析事务传播行为
             * 解析事务超时时间
             * 解析事务隔离级别
             * 解析是否只读
             */
            RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();
            String propagation = methodEle.getAttribute(PROPAGATION_ATTRIBUTE);
            String isolation = methodEle.getAttribute(ISOLATION_ATTRIBUTE);
            String timeout = methodEle.getAttribute(TIMEOUT_ATTRIBUTE);
            String readOnly = methodEle.getAttribute(READ_ONLY_ATTRIBUTE);
            if (StringUtils.isNotBlank(propagation)) {
                attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation);
            }
            if (StringUtils.isNotBlank(isolation)) {
                attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation);
            }
            if (StringUtils.isNotBlank(timeout)) {
                attribute.setTimeoutString(timeout);
            }
            if (StringUtils.isNotBlank(readOnly)) {
                attribute.setReadOnly(Boolean.parseBoolean(methodEle.getAttribute(READ_ONLY_ATTRIBUTE)));
            }

            List<RollbackRuleAttribute> rollbackRules = new ArrayList<>(1);
            if (methodEle.hasAttribute(ROLLBACK_FOR_ATTRIBUTE)) {
                String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR_ATTRIBUTE);
                addRollbackRuleAttributesTo(rollbackRules, rollbackForValue);
            }
            if (methodEle.hasAttribute(NO_ROLLBACK_FOR_ATTRIBUTE)) {
                String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR_ATTRIBUTE);
                addNoRollbackRuleAttributesTo(rollbackRules, noRollbackForValue);
            }
            attribute.setRollbackRules(rollbackRules);
            transactionAttributeMap.put(nameHolder, attribute);
        }

        BeanDefinition attributeSourceDefinition = new BeanDefinition(NameMatchTransactionAttributeSource.class);
        attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap);
        parserContext.registerWithGeneratedName(attributeSourceDefinition);
        return attributeSourceDefinition;
    }

    private void addRollbackRuleAttributesTo(List<RollbackRuleAttribute> rollbackRules, String rollbackForValue) {
        String[] exceptionTypeNames = StringUtils.splitByCharacterType(rollbackForValue);
        for (String typeName : exceptionTypeNames) {
            rollbackRules.add(new RollbackRuleAttribute(StringUtils.trim(typeName)));
        }
    }

    private void addNoRollbackRuleAttributesTo(List<RollbackRuleAttribute> rollbackRules, String noRollbackForValue) {
        String[] exceptionTypeNames = StringUtils.splitByCharacterType(noRollbackForValue);
        for (String typeName : exceptionTypeNames) {
            rollbackRules.add(new NoRollbackRuleAttribute(StringUtils.trim(typeName)));
        }
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        String defaultTMName = "transactionManager";
        /**
         * 定义TransactionInterceptor
         */
        BeanDefinition ti = new BeanDefinition(TransactionInterceptor.class);
        String adviceId = element.getAttribute(ADVICE_ID);
        ti.setId(adviceId);
        parserContext.registerBeanDefinition(adviceId, ti);
        /**
         * 事务管理器
         */
        String transactionManagerName = TxNamespaceHandler.getTransactionManagerName(element);
        ti.getProperties().add(defaultTMName, new RuntimeBeanReference(transactionManagerName));
        List<Element> txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);
        if (txAttributes.size() == 1) {
            Element attributeSourceElement = txAttributes.get(0);
            BeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);
            ti.getProperties().add("transactionAttributeSource", new RuntimeBeanReference(attributeSourceDefinition.getId()));
            ti.getProperties().add("transactionManagerBeanName", new RuntimeBeanNameReference(defaultTMName));
        } else {
            //TODO
        }
        return ti;
    }
}

